import io from 'socket.io-client';
import { Singleton } from 'decorates';
import TimerService from './timer.service';
import KeystoneService from './keystone.service';

export enum SocketStatus {
  /**
   * 成功
   */
  OK = 0,
  /**
   * 参数错误
   */
  INVALID = 1,
  /**
   * 加密验证错误
   */
  SIGN = 2,
  /**
   * 超时
   */
  TIMEOUT = 3,
  /**
   * 无接口访问权限
   */
  NOTPERMITED = 4,
  /**
   * 连接断开
   */
  DISCONNECT = 1000,
  /**
   * 数据库错误
   */
  DB = 101,
  /**
   * 登陆验证错误
   */
  UNAUTHORIZED = 401,
  /**
   * 接口不存在
   */
  NOTFOUND = 404,
  /**
   * 通用错误
   */
  ERROE = 100

}

export class FaceError extends Error {
  /**
   * 错误码
   */
  public code = 0;
  constructor (code: number, message?: string) {
    super(message || `error: ${code}`);
    this.code = code;
  }

  public toString () {
    return `[${this.code}]: ${this.message}\r\n${this.stack}`;
  }
}

/**
 * socket io 返回数据格式
 */
declare type ResponseData = {
  code: number;
  data: Any;
  message: string;
};
/**
 * socketio 数据处理接口函数
 */
declare interface ResponseHandler {
  once: boolean;
  handle: (response: ResponseData) => void;
}
/**
 * 网络服务返回数据格式
 */
declare interface Result {
  success: boolean;
  data?: Any;
  message?: string;
}

/**
 * socketio 服务
 * 封装了全局请求方法和全局监听注册接口
 */
@Singleton()
export default class NetService {
  private timerService = new TimerService();

  private keystone = new KeystoneService();

  /**
   * 网络请求监听集合
   */
  private listeners: { [code: string]: [(response: Any) => void] } = {};
  /**
   * 请求回调集合
   */
  private callbacks: { [key: string]: (response: ResponseData) => void } = {};

  /**
   * socketio对象
   */
  private socket!: WebSocket;
  private apiHost = '';
  private apiPort = 3000;

  constructor () {
    this.apiHost = import.meta.env.VITE_APP_API_HOST as string;
    if (!this.apiHost) {
      this.apiHost = location.hostname;
    }
    this.apiPort = import.meta.env.VITE_APP_API_PORT as Any;
    if (!this.apiPort) {
      this.apiPort = +location.port || 80;
    }
    // 建立连接
    this.reset();
  }

  /**
   * 全局发送消息
   * @param code 代码
   * @param message 信息
   * @param type 方式
   * @param timeout 超时 默认6秒
   */
  public async send (code: string, data: Any = {}, type: 'socket' | 'get' | 'post' = 'socket', timeout = 1000 * 10): Promise<Result> {
    const nonestr = Math.floor(Math.random() * 1000000) + '';
    data = {
      code,
      body: data,
      timestamp: Date.now(),
      nonestr
    };
    try {
      if (type === 'socket') {
        this.socket.send(JSON.stringify({
          ...data,
          token: this.keystone.token
        }));
      } else if (type === 'get') {
        let query = code.replace('.', '/') + '?';
        query += `timestamp=${data.timestamp}`;
        // query += `&token=${data.token}`;
        query += `&nonestr=${data.nonestr}`;
        for (const key in data.body) {
          if (!/[a-zA-Z0-9-_]/g.test(key)) {
            throw new FaceError(SocketStatus.INVALID, '数据键不能有特殊字符 ' + key);
          }
          if (undefined === data.body[key]) {
            continue;
          }
          query += `&${key}=${encodeURIComponent(data.body[key])}`;
        }
        this.fetch(query, { headers: { Authorization: this.keystone.token } }).then(this.onResponse.bind(this));
      } else {
        this.fetch(code.replace('.', '/'), {
          method: 'POST',
          headers: {
            'content-type': 'application/json',
            Authorization: this.keystone.token
          },
          body: JSON.stringify(data)
        }).then(this.onResponse.bind(this));
      }
    } catch (err) {
      this.errorHandler(err);
      return Promise.resolve({
        success: false,
        data: null,
        message: err
      });
    }
    return new Promise((res, rej) => {
      const timer = setTimeout(() => {
        if (this.callbacks[nonestr]) {
          delete this.callbacks[nonestr];
        }
        this.errorHandler(new FaceError(SocketStatus.TIMEOUT));
      }, timeout);
      this.callbacks[nonestr] = (response: ResponseData) => {
        clearTimeout(timer);
        this.callbacks[nonestr] = null as Any;
        delete this.callbacks[nonestr];
        if (+response.code !== SocketStatus.OK) {
          if (+response.code > 399) { // 系统错误
            this.errorHandler(new FaceError(+response.code, response.message as Any));
          }
          res({ success: false, data: response.data, message: response.message });
        } else {
          res({ success: true, data: response.data, message: response.message });
        }
      };
    });
  }

  /**
   * 发送文件
   * @param data 内容
   * @param timeout 超时值
   */
  public upload (path: string, file: File, timeout = 1000 * 60): Promise<Result> {
    const nonestr = Math.floor(Math.random() * 1000000) + '';
    try {
      let data = new FormData();
      data.append('path', path);
      data.append('user', this.keystone.user?.username);
      data.append('timestamp', Date.now() + '');
      data.append('nonestr', nonestr);
      data.append('file', file);
      this.fetch('file/upload', {
        method: 'POST',
        headers: {
          // 'content-type': 'multipart/form-data',
          Authorization: this.keystone.token
        },
        body: data
      }).then(this.onResponse.bind(this));
    } catch (err) {
      this.errorHandler(err);
      return Promise.resolve({
        success: false,
        data: null
      });
    }
    return new Promise((res, rej) => {
      const timer = setTimeout(() => {
        if (this.callbacks[nonestr]) {
          delete this.callbacks[nonestr];
        }
        this.errorHandler(new FaceError(SocketStatus.TIMEOUT));
      }, timeout);
      this.callbacks[nonestr] = (response: ResponseData) => {
        clearTimeout(timer);
        this.callbacks[nonestr] = null as Any;
        delete this.callbacks[nonestr];
        if (+response.code !== SocketStatus.OK) {
          if (+response.code > 399) { // 系统错误
            this.errorHandler(new FaceError(+response.code, response.message as Any));
          }
          res({ success: false, data: response.data, message: response.message });
        } else {
          res({ success: true, data: response.data, message: response.message });
        }
      };
    });
  }

  /**
   * 全局监听socketio事件
   * @param code 命令
   * @param handle 处理接口
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  public on (code: string, handle: (response: any) => void, unique = false) {
    if (unique) {
      this.listeners[code] = [handle];
    } else {
      this.listeners[code] = this.listeners[code] || [];
      this.listeners[code].push(handle);
    }
    return true;
  }

  /**
   * 取消全局监听socketio事件
   * @param code 命令 * 代表清除所有
   * @param handle 处理接口
   */
  public off (code: string, handle?: (response: Any) => void) {
    if (code === '*') {
      this.listeners = {};
      return true;
    } else {
      this.listeners[code] = this.listeners[code] || [];
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      let index = this.listeners[code].indexOf(handle!);
      if (index > -1) {
        this.listeners[code].splice(index);
        return true;
      } else {
        return false;
      }
    }
  }

  /**
   * 设置身份令牌
   * @param token 令牌
   */
  public setToken (token: string) {
    this.keystone.token = token;
  }

  public errorHandler: (err: FaceError) => void = (err) => {
    console.error(err);
  }

  /**
   * 以二进制方式发送数据
   * @param data 数据
   */
  private emit (data: ArrayBuffer, timeout = 1000 * 6): Promise<Result> {
    const that = this;
    let nonestr = Math.floor(Math.random() * 100000000) + '';
    nonestr += new Array(8 - nonestr.length).fill('0').join('');
    // 发送
    data = Buffer.concat([
      Buffer.from(this.keystone.token + '|', 'ascii'),
      Buffer.from(nonestr + '|', 'ascii'),
      Buffer.from(data)
    ]);
    this.socket.send(data);
    return new Promise((res, rej) => {
      const timer = setTimeout(() => {
        if (that.callbacks[nonestr]) {
          delete that.callbacks[nonestr];
        }
        rej(new FaceError(SocketStatus.TIMEOUT));
      }, timeout);
      that.callbacks[nonestr] = (response: ResponseData) => {
        clearTimeout(timer);
        delete that.callbacks[nonestr];
        let status = +response.code;
        if (status !== SocketStatus.OK) {
          // rej({
          //   success: false,
          //   data: null
          // });
          rej(new FaceError(SocketStatus.ERROE));
        } else {
          res({
            success: true,
            data: response.data
          });
        }
      };
    });
  }

  /**
   * 接收socketio返回的消息
   * @param res 回复消息体
   */
  private onResponse (res: Any) {
    try {
      let response = res;
      if (typeof res === 'string') {
        response = JSON.parse(res);
      }
      if (this.callbacks[response.nonestr]) {
        this.callbacks[response.nonestr](response);
      }
    } catch (err) {
      if (err instanceof FaceError) {
        this.errorHandler(err);
      } else {
        console.log(`接口返回数据异常 ${res}`);
      }
    }
  }

  /**
   * 接收socketio 的请求消息
   * @param request 回复消息体
   */
  private onRequest (request: {code: string}) {
    // 解析数据
    // 调用监听接口
    if (this.listeners[request.code]) {
      const errs: string[] = [];
      for (const handle of this.listeners[request.code]) {
        try {
          handle(request);
        } catch (err) {
          errs.push(err.stack);
        }
      }
      if (errs.length) {
        throw new FaceError(101, errs.join('\r\n'));
      }
    }
  }

  /**
   * 重置连接
   */
  private reset () {
    /*
    if (this.socket) {
      this.socket.close();
    }
    try {
      this.socket = new WebSocket(`ws://${this.apiHost}:${this.apiPort}/ws`);
      this.socket.onmessage = data => {
        this.onResponse(data?.data);
      };
      this.socket.onerror = err => {
        console.error(err);
      };
      this.socket.onclose = () => {
        setTimeout(this.reset.bind(this), 1000);
      };
    } catch (err) {
      throw new FaceError(SocketStatus.DISCONNECT, err);
    }
    */
  }

  /**
   * 发起http请求.
   *
   * @param url - 地址.
   * @param request - 请求参数.
   * @param timeout - 超时设置.
   * @returns 返回 封装后的 `Promise<any|Error>`
   */
  private fetch (url: string, request: Any) {
    return fetch(`http://${this.apiHost}:${this.apiPort}/api/${url}`, request)
      .then(async res => {
        return await res.json();
      });
  }
}
